# 传递实参
# 函数定义中可能包含多个形参，因此函数调用中也可能包含多个实参。
# 向函数传递实参的方式很多：可使用位置实参 ，这要求实参的顺序与形参的顺序相同；
# 也可使用关键字实参 ，其中每个实参都由变量名和值组成；还可使用列表和字典。
# 下面依次介绍这些方式。

# 位置实参
# 调用函数时，Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。
# 为此，最简单的关联方式是基于实参的顺序。这种关联方式称为位置实参 。
# pets.py 显示宠物信息的函数，该函数指出一个宠物属于哪种动物以及它叫什么名字
def describe_pet(animal_type, pet_name):
	"""显示宠物信息"""
	print(f"\nI have a {animal_type}.")
	print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet("hamster", "harry")
describe_pet('dog', 'willie')
# 调用describe_pet() 时，需要按顺序提供一种动物类型和一个名字。
# 例如，在刚才的函数调用中，实参'hamster'(仓鼠) 被赋给形参animal_type ，而实参'harry' 被赋给形参pet_name

# 多次调用函数是一种效率极高的工作方式。只需在函数中编写
# 一次描述宠物的代码，然后每当需要描述新宠物时，都调用该
# 函数并向它提供新宠物的信息。即便描述宠物的代码增加到了
# 10行，依然只需使用一行调用函数的代码，就可描述一个新宠物。
# 在函数中，可根据需要使用任意数量的位置实参，Python将按顺序将函数调用中的实参关联到函数定义中相应的形参。

# 位置实参的顺序很重要
# 使用位置实参来调用函数时，如果实参的顺序不正确，结果可能出乎意料：
describe_pet('harry', 'hamster')
# 在这个函数调用中，先指定名字，再指定动物类型。由于实参'harry' 在前，这个值将赋给形参animal_type 。
# 同理，'hamster' 将赋给形参pet_name 。结果是有一个名为Hamster的harry
# 如果你得到的结果像上面一样可笑，请确认函数调用中实参的顺序与函数定义中形参的顺序一致


# 关键字实参
# 关键字实参 是传递给函数的名称值对。因为直接在实参中将名称
# 和值关联起来，所以向函数传递实参时不会混淆（不会得到名为Hamster的harry这样的结果）。
# 关键字实参让你无须考虑函数调用中的实参顺序，还清楚地指出了函数调用中各个值的用途。
# 重新编写pets.py，在其中使用关键字实参来调用describe_pet() ：
def describe_pet_new(animal_type, pet_name):
	"""显示宠物的信息"""
	print(f"\nI have a {animal_type}.")
	print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet_new(animal_type='hamster', pet_name='harry')
# 函数describe_pet() 还和之前一样，但调用这个函数时，向Python明确地指出了各个实参对应的形参。
# 看到这个函数调用时，Python知道应该将实参'hamster' 和'harry' 分别赋给形参animal_type 和pet_name 。
# 输出正确无误，指出有一只名为Harry的仓鼠。
# 关键字实参的顺序无关紧要，因为Python知道各个值该赋给哪个形参。
# 下面这个函数调用是与上面等效的：
describe_pet(pet_name='harry', animal_type='hamster')

# 注意 　使用关键字实参时，务必准确指定函数定义中的形参名。


# 默认值
# 编写函数时，可给每个形参指定默认值 。在调用函数中给形参提供了实参时，Python将使用指定的实参值；
# 否则，将使用形参的默认值。因此，给形参指定默认值后，可在函数调用中省略相应的实参。
# 使用默认值可简化函数调用，还可清楚地指出函数的典型用法。
# 例如，如果你发现调用describe_pet() 时，描述的大多是小狗，就可将形参animal_type 的默认值设置为'dog' 。
# 这样，调用describe_pet() 来描述小狗时，就可不提供这种信息：
def describe_pet(pet_name, animal_type='dog'):
	"""显示宠物的信息"""
	print(f"\nI have a {animal_type}.")
	print(f"My {animal_type}'s name is {pet_name.title()}.")

describe_pet(pet_name='willie')
# 这里修改了函数describe_pet() 的定义，在其中给形参animal_type 指定了默认值'dog' 。
# 这样，调用这个函数时，如果没有给animal_type 指定值，Python就将把这个形参设置为'dog'

# 请注意，在这个函数的定义中，修改了形参的排列顺序。因为给
# animal_type 指定了默认值，无须通过实参来指定动物类型，所
# 以在函数调用中只包含一个实参——宠物的名字。然而，Python依
# 然将这个实参视为位置实参，因此如果函数调用中只包含宠物的名
# 字，这个实参将关联到函数定义中的第一个形参。这就是需要将pet_name 放在形参列表开头的原因
# 使用这个函数的最简单方式是在函数调用中只提供小狗的名字：
describe_pet('willie')	# 这个实参将关联到函数定义中的第一个形参pet_name 。
# 由于没有给animal_type 提供实参，Python将使用默认值'dog'

# 如果要描述的动物不是小狗，可使用类似于下面的函数调用:
describe_pet(pet_name='harry', animal_type='hamster')
# 上面语句由于显式地给animal_type 提供了实参，Python将忽略这个形参的默认值。

# 注意 　使用默认值时，必须先在形参列表中列出没有默认值的形参，
# 再列出有默认值的实参。这让Python依然能够正确地解读位置实参。


# 等效的函数调用
# 鉴于可混合使用位置实参、关键字实参和默认值，通常有多种等效的函数调用方式。
# 下面对def describe_pet(pet_name, animal_type='dog'):这个函数的所有调用都可行:
# 一只名为Willie的小狗
describe_pet('willie')
describe_pet(pet_name='willie')
# 一只名为Harry的仓鼠
describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')

# 注意 　使用哪种调用方式无关紧要，只要函数调用能生成你期望的输出就行。使用对你来说最容易理解的调用方式即可。


# 避免实参错误
# 等你开始使用函数后，如果遇到实参不匹配错误，不要大惊小怪。
# 你提供的实参多于或少于函数完成工作所需的信息时，将出现实参不匹配错误

# 例如，如果调用函数describe_pet() 时没有指定任何实参，结果将如何呢？
def describe_pet(animal_type, pet_name):
	"""显示宠物的信息。"""
	print(f"\nI have a {animal_type}.")
	print(f"My {animal_type}'s name is {pet_name.title()}.")
# describe_pet()		# Python发现该函数调用缺少必要的信息，traceback将指出这一点

# 如果提供的实参太多，将出现类似的traceback，帮助你确保函数调用和函数定义匹配。
